package com.imooc.uaa.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.uaa.security.filter.MyUsernamePasswordAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.*;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.sql.DataSource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 江黎
 * @date 2022-02-11
 */
@EnableWebSecurity/*(debug = true)*/
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

//        http.authorizeRequests(req -> req.antMatchers("/api/greeting").permitAll());

//        http.formLogin(Customizer.withDefaults())
//                .authorizeRequests(req -> req.antMatchers("/api/greeting").authenticated());

//        http.csrf(csrf -> csrf.disable())
//                .httpBasic(Customizer.withDefaults())
//                .formLogin(Customizer.withDefaults())
////                .formLogin(form -> form.disable()) // 禁用默认的登录页,使用弹框的方式输入账号密码登录
//                .authorizeRequests(req -> req.antMatchers("/api/**").authenticated());

//        http.authorizeRequests(req -> req.antMatchers("/api/**").authenticated())
/*        http.authorizeRequests(req -> req.anyRequest().authenticated())
                .formLogin(form -> form.loginPage("/login").permitAll()
                                .usernameParameter("username").passwordParameter("password")
                                .loginProcessingUrl("/login")
                                // 默认登录成功后跳转的url
//                        .defaultSuccessUrl("/", true)
                                // 登录成功后的处理:返回json
                                .successHandler(getAuthenticationSuccessHandler())
                                .failureHandler(getAuthenticationFailureHandler())
                )
                // 用户无访问权限的处理器
                .exceptionHandling(getExceptionHandlingConfigurerCustomizer())
                .httpBasic(Customizer.withDefaults())
                .logout(logout -> logout.logoutUrl("/logout").logoutSuccessUrl("/login").deleteCookies().permitAll()
                        .logoutSuccessHandler(getLogoutSuccessHandler()))
                .rememberMe(remember -> remember.tokenValiditySeconds(30 * 24 * 3600))
                .csrf(AbstractHttpConfigurer::disable)
                // LoginUrlAuthenticationEntryPoint.commence()未认证就访问数据，默认直接重定向到登录页面.在前后分离中重写该方法返回JSON让前端处理跳转
                .exceptionHandling(getExceptionHandlingConfigurerCustomizerWithNoLogin());*/

        http.authorizeRequests(req -> req
                .antMatchers("/authorize/**").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/api/**").hasRole("USER")
                .anyRequest().authenticated())
                // 替换过滤器
                .addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .csrf(AbstractHttpConfigurer::disable);

    }

    private MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
        MyUsernamePasswordAuthenticationFilter filter = new MyUsernamePasswordAuthenticationFilter();
        filter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler());
        filter.setAuthenticationFailureHandler(getAuthenticationFailureHandler());
        filter.setAuthenticationManager(authenticationManager());
        filter.setFilterProcessesUrl("/authorize/login");
        return filter;
    }


    private Customizer<ExceptionHandlingConfigurer<HttpSecurity>> getExceptionHandlingConfigurerCustomizerWithNoLogin() {
        return exceptionHandlingCustomizer -> exceptionHandlingCustomizer.authenticationEntryPoint((req, rep, authException) -> {
                    ObjectMapper objectMapper = new ObjectMapper();
                    rep.setStatus(HttpStatus.UNAUTHORIZED.value());
                    rep.setContentType(MediaType.APPLICATION_JSON_VALUE);
                    rep.setCharacterEncoding(StandardCharsets.UTF_8.name());
                    Map<String, Object> result = new HashMap<>();
                    result.put("code", 401);
                    result.put("msg", "尚未登录，请先登录");
                    rep.getWriter().write(objectMapper.writeValueAsString(result));
                }
        );
    }

    private Customizer<ExceptionHandlingConfigurer<HttpSecurity>> getExceptionHandlingConfigurerCustomizer() {
        return (exceptionHandlingCustomizer) -> exceptionHandlingCustomizer.accessDeniedHandler((req, rep, authentication) -> {
            ObjectMapper objectMapper = new ObjectMapper();
            rep.setStatus(HttpStatus.FORBIDDEN.value());
            rep.setContentType(MediaType.APPLICATION_JSON_VALUE);
            rep.setCharacterEncoding(StandardCharsets.UTF_8.name());
            Map<String, Object> result = new HashMap<>();
            result.put("code", 403);
            result.put("msg", "您没有权限访问当前资源");
            rep.getWriter().write(objectMapper.writeValueAsString(result));
        });
    }

    private AuthenticationSuccessHandler getAuthenticationSuccessHandler() {
        return (req, rep, auth) -> {
            ObjectMapper objectMapper = new ObjectMapper();
            rep.getWriter().write(objectMapper.writeValueAsString(auth));
        };
    }

    private AuthenticationFailureHandler getAuthenticationFailureHandler() {
        return (req, rep, ex) -> {
            ObjectMapper objectMapper = new ObjectMapper();
            rep.setStatus(HttpStatus.UNAUTHORIZED.value());
            rep.setContentType(MediaType.APPLICATION_JSON_VALUE);
            rep.setCharacterEncoding(StandardCharsets.UTF_8.name());
            Map<String, Object> result = new HashMap<>();
            if (ex instanceof LockedException) {
                result.put("msg", "账户被锁定，请联系管理员!");
            } else if (ex instanceof CredentialsExpiredException) {
                result.put("msg", "密码过期，请联系管理员!");
            } else if (ex instanceof AccountExpiredException) {
                result.put("msg", "账户过期，请联系管理员!");
            } else if (ex instanceof DisabledException) {
                result.put("msg", "账户被禁用，请联系管理员!");
            } else if (ex instanceof BadCredentialsException) {
                result.put("msg", "用户名或者密码输入错误，请重新输入!");
            }
            result.put("code", 401);
            rep.getWriter().write(objectMapper.writeValueAsString(result));
        };
    }

    private LogoutSuccessHandler getLogoutSuccessHandler() {
        return (req, rep, authentication) -> {
            ObjectMapper objectMapper = new ObjectMapper();
            rep.setContentType(MediaType.APPLICATION_JSON_VALUE);
            rep.setCharacterEncoding(StandardCharsets.UTF_8.name());
            Map<String, Object> result = new HashMap<>();
            result.put("code", 200);
            result.put("msg", "注销成功");
            rep.getWriter().write(objectMapper.writeValueAsString(result));
        };
    }

    /**
     * 此处配置的请求不会进入的过滤器链,直接放行
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .mvcMatchers("/error", "/h2-console/**")
                // 对常用的静态资源路径放行
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .withDefaultSchema()
                .dataSource(dataSource)
                .withUser("user").password(passwordEncoder().encode("12345678")) .roles("ADMIN", "USER");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


}
